﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Extensions.Internal;
using SaiLing.Domain.Persistence.Relational;
using SaiLing.Domain.Specification;
using SaiLing.Repository;

namespace SaiLing.SingleSignOn.Repository
{
    public class BaseRepository<T> : IRepository<T> where T : IEntity, new()
    {
        private BaseDbContext _dataContext;
        protected readonly DbSet<T> Dbset;

        protected BaseRepository(IDatabaseFactory<ProjectDbContext> databaseFactory)
        {
            DatabaseFactory = databaseFactory;
            Dbset = DataContext.Set<T>();
        }

        protected IDatabaseFactory<ProjectDbContext> DatabaseFactory
        {
            get;
            private set;
        }

        protected BaseDbContext DataContext => _dataContext ?? (_dataContext = DatabaseFactory.Get());


        #region IRepository<T,PageData<T>> 成员

        public virtual IQueryable<T> All<TS>(Expression<Func<T, TS>> orderByExpression, bool isDesc = true)
        {
            var queryable = isDesc
                ? Dbset.AsNoTracking().OrderByDescending(orderByExpression)
                : Dbset.AsNoTracking().OrderBy(orderByExpression);
            return queryable;
        }

        public virtual IPagedList<T> PageList<TS>(int pageIndex, int pageSize, ISpecification<T> specification, Expression<Func<T, TS>> orderByExpression, bool isDesc, bool? asNoTracking = true)
        {
            var query = isDesc
                     ?
                     Dbset.Where(specification.SatisfiedBy()).OrderByDescending(orderByExpression)
                     :
                     Dbset.Where(specification.SatisfiedBy()).OrderBy(orderByExpression);
            return query.ToPagedList(pageIndex, pageSize);
        }

        public virtual IEnumerable<T> GetAll(bool? asNoTracking = true)
        {
            if (asNoTracking == true) return Dbset.Where(x => x.LogicRemove == false).AsNoTracking();
            return Dbset.Where(x => x.LogicRemove == false);
        }

        public IEnumerable<T> GetMany(ISpecification<T> specification, bool? asNoTracking = true)
        {
            if (asNoTracking == true) return Dbset.Where(specification.SatisfiedBy()).AsNoTracking();
            return Dbset.Where(specification.SatisfiedBy());
        }

        public virtual IEnumerable<T> GetListByTopN<TS>(int topN, ISpecification<T> specification, Expression<Func<T, TS>> orderByExpression, bool isDesc, bool? asNoTracking = true)
        {
            var query = isDesc
                 ?
                 Dbset.Where(specification.SatisfiedBy()).OrderByDescending(orderByExpression).Take(topN)
                 :
                 Dbset.Where(specification.SatisfiedBy()).OrderBy(orderByExpression).Take(topN);

            return asNoTracking == true ? query.AsNoTracking().ToList() : query.ToList();
        }

        public virtual Task<T> GetByCondition(ISpecification<T> specification, bool? asNoTracking = false)
        {
            return asNoTracking == false ? Dbset.FirstOrDefaultAsync(specification.SatisfiedBy()) : Dbset.AsNoTracking().FirstOrDefaultAsync(specification.SatisfiedBy());
        }

        public virtual Task<T> GetByKey(object key)
        { 
            return Dbset.FindAsync(key);
        }

        public virtual Task Add(T entity)
        {
           return Dbset.AddAsync(entity);
        }

        public virtual void Modify(T entity)
        {
            DataContext.Entry(entity).State = EntityState.Modified;
        }

        public void Update(T entity, params Expression<Func<T, object>>[] properties)
        {
            DataContext.AttachUpdated(entity);
            var entry = DataContext.Entry(entity);
            foreach (var selector in properties)
            { entry.Property(selector).IsModified = true; }
        }

        public virtual void Remove(T entity)
        {
            Dbset.Remove(entity);
        }

        public virtual Task Remove(ISpecification<T> specification)
        {
            return Task.Factory.StartNew(() =>
            {
                var objects = Dbset.Where(specification.SatisfiedBy()).AsEnumerable();
                foreach (T obj in objects)
                    Dbset.Remove(obj);
            });
        }

        public virtual void LogicRemove(T entity)
        {
            entity.LogicRemove = true;
            Update(entity, x => x.LogicRemove);
        }

        public virtual Task LogicRemove(ISpecification<T> specification)
        {
            return Task.Factory.StartNew(() =>
            {
                var objects = Dbset.Where(specification.SatisfiedBy()).AsEnumerable();
                foreach (T obj in objects)
                    LogicRemove(obj);
            });
        }

        public virtual Task<bool> ExistsAsync(ISpecification<T> specification)
        {
            return Dbset.AnyAsync(specification.SatisfiedBy());
        }
        #endregion


        public int GetCount()
        {
            return Dbset.Count();
        }

        public int GetCount(ISpecification<T> specification)
        {
            return Dbset.Where(specification.SatisfiedBy()).Count();
        }
    }
}
